package com.nulldev.util.io.serialization;

import java.io.*;
import java.nio.ByteBuffer;
import java.util.Arrays;

/**
 * Utility class which implements DataInput and DataOutput on top of byte[]
 * buffer with minimal overhead
 *
 * @author Jan Kotek
 */
class DataInputOutput implements DataInput, DataOutput, ObjectInput, ObjectOutput {

	private int pos = 0;
	private int count = 0;
	private byte[] buf;

	public DataInputOutput() {
		buf = new byte[8];
	}

	public DataInputOutput(int size) {
		buf = new byte[size];
	}

	public DataInputOutput(byte[] data) {
		buf = data;
		count = data.length;
	}

	public byte[] getBuf() {
		return buf;
	}

	public int getPos() {
		return pos;
	}

	public void reset() {
		pos = 0;
		count = 0;
	}

	public void resetForReading() {
		count = pos;
		pos = 0;
	}

	public void reset(byte[] b) {
		pos = 0;
		buf = b;
		count = b.length;
	}

	public byte[] toByteArray() {
		byte[] d = new byte[pos];
		System.arraycopy(buf, 0, d, 0, pos);
		return d;
	}

	public int available() {
		return count - pos;
	}

	public void readFully(byte[] b) throws IOException {
		readFully(b, 0, b.length);
	}

	public void readFully(byte[] b, int off, int len) throws IOException {
		System.arraycopy(buf, pos, b, off, len);
		pos += len;
	}

	public int skipBytes(int n) throws IOException {
		pos += n;
		return n;
	}

	public boolean readBoolean() throws IOException {
		return buf[pos++] == 1;
	}

	public byte readByte() throws IOException {
		return buf[pos++];
	}

	public int readUnsignedByte() throws IOException {
		return buf[pos++] & 0xff;
	}

	public short readShort() throws IOException {
		return (short) (((short) (buf[pos++] & 0xff) << 8) | ((short) (buf[pos++] & 0xff) << 0));

	}

	public int readUnsignedShort() throws IOException {
		return (((int) (buf[pos++] & 0xff) << 8) | ((int) (buf[pos++] & 0xff) << 0));
	}

	public char readChar() throws IOException {
		return (char) readInt();
	}

	public int readInt() throws IOException {
		return (((buf[pos++] & 0xff) << 24) | ((buf[pos++] & 0xff) << 16) | ((buf[pos++] & 0xff) << 8) | ((buf[pos++] & 0xff) << 0));

	}

	public long readLong() throws IOException {
		return (((long) (buf[pos++] & 0xff) << 56) | ((long) (buf[pos++] & 0xff) << 48) | ((long) (buf[pos++] & 0xff) << 40)
				| ((long) (buf[pos++] & 0xff) << 32) | ((long) (buf[pos++] & 0xff) << 24) | ((long) (buf[pos++] & 0xff) << 16)
				| ((long) (buf[pos++] & 0xff) << 8) | ((long) (buf[pos++] & 0xff) << 0));

	}

	public float readFloat() throws IOException {
		return Float.intBitsToFloat(readInt());
	}

	public double readDouble() throws IOException {
		return Double.longBitsToDouble(readLong());
	}

	public String readLine() throws IOException {
		return readUTF();
	}

	public String readUTF() throws IOException {
		return Serialization.deserializeString(this);
	}

	/**
	 * make sure there will be enough space in buffer to write N bytes
	 */
	private void ensureAvail(int n) {
		if (pos + n >= buf.length) {
			int newSize = Math.max(pos + n, buf.length * 2);
			buf = Arrays.copyOf(buf, newSize);
		}
	}

	public void write(int b) throws IOException {
		ensureAvail(1);
		buf[pos++] = (byte) b;
	}

	public void write(byte[] b) throws IOException {
		write(b, 0, b.length);
	}

	public void write(byte[] b, int off, int len) throws IOException {
		ensureAvail(len);
		System.arraycopy(b, off, buf, pos, len);
		pos += len;
	}

	public void writeBoolean(boolean v) throws IOException {
		ensureAvail(1);
		buf[pos++] = (byte) (v ? 1 : 0);
	}

	public void writeByte(int v) throws IOException {
		ensureAvail(1);
		buf[pos++] = (byte) (v);
	}

	public void writeShort(int v) throws IOException {
		ensureAvail(2);
		buf[pos++] = (byte) (0xff & (v >> 8));
		buf[pos++] = (byte) (0xff & (v >> 0));

	}

	public void writeChar(int v) throws IOException {
		writeInt(v);
	}

	public void writeInt(int v) throws IOException {
		ensureAvail(4);
		buf[pos++] = (byte) (0xff & (v >> 24));
		buf[pos++] = (byte) (0xff & (v >> 16));
		buf[pos++] = (byte) (0xff & (v >> 8));
		buf[pos++] = (byte) (0xff & (v >> 0));

	}

	public void writeLong(long v) throws IOException {
		ensureAvail(8);
		buf[pos++] = (byte) (0xff & (v >> 56));
		buf[pos++] = (byte) (0xff & (v >> 48));
		buf[pos++] = (byte) (0xff & (v >> 40));
		buf[pos++] = (byte) (0xff & (v >> 32));
		buf[pos++] = (byte) (0xff & (v >> 24));
		buf[pos++] = (byte) (0xff & (v >> 16));
		buf[pos++] = (byte) (0xff & (v >> 8));
		buf[pos++] = (byte) (0xff & (v >> 0));
	}

	public void writeFloat(float v) throws IOException {
		ensureAvail(4);
		writeInt(Float.floatToIntBits(v));
	}

	public void writeDouble(double v) throws IOException {
		ensureAvail(8);
		writeLong(Double.doubleToLongBits(v));
	}

	public void writeBytes(String s) throws IOException {
		writeUTF(s);
	}

	public void writeChars(String s) throws IOException {
		writeUTF(s);
	}

	public void writeUTF(String s) throws IOException {
		Serialization.serializeString(this, s);
	}

	/** helper method to write data directly from PageIo */
	public void writeFromByteBuffer(ByteBuffer b, int offset, int length) {
		ensureAvail(length);
		b.position(offset);
		b.get(buf, pos, length);
		pos += length;
	}

	// temp var used for Externalizable
	SerialClassInfo serializer;
	// temp var used for Externalizable
	@SuppressWarnings("rawtypes")
	Serialization.FastArrayList objectStack;

	public Object readObject() throws ClassNotFoundException, IOException {
		// is here just to implement ObjectInput
		// Fake method which reads data from serializer.
		// We could probably implement separate wrapper for this, but I want to safe
		// class space
		return serializer.deserialize(this, objectStack);
	}

	public int read() throws IOException {
		// is here just to implement ObjectInput
		return readUnsignedByte();
	}

	public int read(byte[] b) throws IOException {
		// is here just to implement ObjectInput
		readFully(b);
		return b.length;
	}

	public int read(byte[] b, int off, int len) throws IOException {
		// is here just to implement ObjectInput
		readFully(b, off, len);
		return len;
	}

	public long skip(long n) throws IOException {
		// is here just to implement ObjectInput
		pos += n;
		return n;
	}

	public void close() throws IOException {
		// is here just to implement ObjectInput
		// do nothing
	}

	public void writeObject(Object obj) throws IOException {
		// is here just to implement ObjectOutput
		serializer.serialize(this, obj, objectStack);
	}

	public void flush() throws IOException {
		// is here just to implement ObjectOutput
		// do nothing
	}

}